/*
    Copyright (c) 2010, Kozak Paweł (haango.mbox@gmail.com)
    All rights reserved.


    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:
        * Redistributions of source code must retain the above copyright
        notice, this list of conditions and the following disclaimer.
        * Redistributions in binary form must reproduce the above copyright
        notice, this list of conditions and the following disclaimer in the
        documentation and/or other materials provided with the distribution.
        * Neither the name of the <organization> nor the
        names of its contributors may be used to endorse or promote products
        derived from this software without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY <copyright holder> ''AS IS'' AND ANY
    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

#include "LaunchManager.h"
#include <QSqlError>
#include <QDebug>
#include <QSqlQuery>
#include <QStringList>
#include <QSqlTableModel>
#include <QSqlRecord>
#include <QSqlDriver>
#include <QFile>
#include "ListErrors.h"
#include <QProcess>
#include "EmitError.h"



///////////////////////////////////////////////////////////////////////////////////////////////////
// Initialization of static non-integral type variables
///////////////////////////////////////////////////////////////////////////////////////////////////

const QString LaunchManager::driver( "QSQLITE" );
const QString LaunchManager::databaseFileName( "desktop-launcher.db" );

///////////////////////////////////////////////////////////////////////////////////////////////////
// SQL Command Section
///////////////////////////////////////////////////////////////////////////////////////////////////

/**
  * Skrypt SQL tworzący tabelę do pamiętania
  * informacji na temat wprowadzonych programów
  * do systemu aplikacji.
  *
  * Tabela nie pozwala trzymać identycznych ścieżek
  * dostępu do aplikacji.
  *
  * Jeżeli użytkownik nie wprowadzi ikony dla aplikacji,
  * to zostanie zastosowana domyślna ikona.
  *
  * id       - klucz główny tabeli
  * fileName - ścieżka dostępu do wprowadzonej aplikacji
  * icon     - ścieżka dostępu do ikony aplikacji
  */
static const QString sqlCommandCreateTablePrograms(
    "CREATE TABLE Programs ("
    "id INTEGER PRIMARY KEY AUTOINCREMENT,"
    "fileName TEXT NOT NULL UNIQUE,"
    "icon TEXT NOT NULL DEFAULT './icon/default.ico')"
);

/**
  * Skrypt SQL tworzący tabelę do pamiętania
  * informacji na temat wprowadzonych launcherów.
  *
  * Launcher w systemie aplikacji oznacza nazwę,
  * zdefiniowaną przez użytkownika, pozwalającą
  * na uruchomienie obranej przez niego aplikacji.
  *
  * Dla jednego programu użytkownik może utworzyć
  * nieokreślone liczby launcherów.
  *
  * Każdy launcher musi być różny w nazwie, aby wykluczyć
  * zdarzenia polegające na uruchomieniu wielu aplikacji
  * jednym launcherem.
  *
  * id         - klucz główny tabeli
  * name       - nazwa launchera
  * id_program - id programu, któremu odpowiada launcher
  */
static const QString sqlCommandCreateTableLaunchers(
    "CREATE TABLE Launchers ("
    "id INTEGER PRIMARY KEY AUTOINCREMENT,"
    "name TEXT NOT NULL UNIQUE,"
    "id_program INTEGER NOT NULL,"
    "FOREIGN KEY (id_program) REFERENCES Programs(id))"
);

/**
  * Skrypt SQL usuwający istniejącą tabelę
  * wprowadzonych programów.
  */
static const QString sqlCommandDropTablePrograms(
    "DROP TABLE IF EXISTS Programs"
);

/**
  * Skrypt SQL usuwający istniejącą tabelę
  * wprowadzonych launcherów.
  */
static const QString sqlCommandDropTableLaunchers(
    "DROP TABLE IF EXISTS Launchers"
);

/**
  * Skrypt SQL wprowadzający rekord
  * do tabeli programów.
  *
  * %1 - ścieżka dostępu do programu
  * %2 - ścieżka dostępu do ikony
  */
static const QString sqlCommandInsertProgram(
    "INSERT INTO Programs (fileName, icon)"
    "VALUES ('%1', '%2')"
);

/**
  * Skrypt SQL wprowadzający rekord
  * do tabeli programów, w tym przypadku
  * użyta zostanie domyślna ikona.
  *
  * %1 - ścieżka dostępu do programu
  */
static const QString sqlCommandInsertProgramWithDefaultIcon(
    "INSERT INTO Programs (fileName) "
    "VALUES ('%1')"
);

/**
  * Skrypt SQL wprowadzający rekrod
  * do tabeli launcherów.
  *
  * %1 - nazwa launchera
  * %2 - id programu
  */
static const QString sqlCommandInsertLauncher(
    "INSERT INTO Launchers (name, id_program) "
    "VALUES('%1', %2)"
);

/**
  * Skrypt SQL pobierający z tabeli
  * id programu na podstawie ścieżki dostępu.
  *
  * %1 - ścieżka dostępu
  */
static const QString sqlCommandSelectIdProgram(
    "SELECT id FROM Programs "
    "WHERE fileName == '%1'"
);

/**
  * Skrypt SQL pobierajacy z tabeli
  * nazwę programu, któremu odpowiada
  * dany launcher.
  *
  * %1 - nazwa launchera
  */
static const QString sqlCommandSelectFileName(
    "SELECT fileName "
    "FROM Programs AS P "
    "INNER JOIN Launchers AS L "
    "ON P.id == L.id_program "
    "WHERE L.name == '%1'"
);

/**
  * Skrypt SQL edytujący ścieżke dostępu
  * do programu.
  *
  * %1 - stara wartość ścieżki dostępu
  * %2 - nowa wartość ścieżki dostępu
  */
static const QString sqlCommandUpdateFileName(
    "UPDATE Programs "
    "SET fileName = '%2' "
    "WHERE fileName == '%1'"
);

/**
  * Skrypt SQL edytujący ścieżkę dostępu
  * do ikony programu.
  *
  * %1 - ścieżki dostepu do programu
  * %2 - nowa wartość ścieżki dostępu do ikony
  */
static const QString sqlCommandUpdateIcon(
    "UPDATE Programs "
    "SET icon = '%2' "
    "WHERE fileName == '%1'"
);

/**
  * Skrypt SQL edytujacy nazwę launchera.
  *
  * %1 - stara wartośc nazwy launchera
  * %2 - nowa wartość nazwy launchera
  */
static const QString sqlCommandUpdateLauncher(
    "UPDATE Launchers "
    "SET name = '%2' "
    "WHERE name == '%1'"
);

/**
  * Skrypt SQL usuwający wskazany launcher.
  *
  * %1 - nazwa launchera
  */
static const QString sqlCommandRemoveLauncher(
    "DELETE FROM Launchers "
    "WHERE name == '%1'"
);

/**
  * Skrypt SQL usuwający wskazany program.
  *
  * %1 - id programu
  */
static const QString sqlCommandRemoveProgram(
    "DELETE FROM Programs "
    "WHERE id == %1"
);

/**
  * Skrypt SQL usuwający wszystkie launchery
  * wskazanego programu.
  *
  * %1 - id programu
  */
static const QString sqlCommandRemoveAllProgramLaunchers(
    "DELETE FROM Launchers "
    "WHERE id_program == %1"
);

///////////////////////////////////////////////////////////////////////////////////////////////////
// Destructor
///////////////////////////////////////////////////////////////////////////////////////////////////

LaunchManager::~LaunchManager() {
    if ( database.isOpen() )
        database.close();
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// Public Method
///////////////////////////////////////////////////////////////////////////////////////////////////

/**
  * // TODO usuń tę metodę w kolejnej wersji kodu
  *
  * Metoda w kodzie występuje gościnie, a jej
  * celem jest przedstawienie korzystania z API
  * LaunchManager'a.
  */
void LaunchManager::showExample() {

    if ( !prepare() )
        return;

    // Przygotowanie wpisu do bazy danych.
    QStringList launchers;

    launchers << "notepad" << "gedit" << "editor" << "ge";

    QString fileName = "/usr/bin/gedit";
    
    // dodanie launchera
    if (!addLaunchers( launchers, fileName ) )
	return;
    
    // edycja launchera
    if (!editLauncher("ge", "GNU-Editor"))
	return;
    
    // usuniecie jednego z launcherów
    if (!removeLauncher("notepad"))
	return;
    
    // uruchomienie aplikacji
    if (!launch("editor"))
	return;
    
    qDebug() << "EXIT SUCCESS";
}

/**
  * Przygotowanie managera do pracy.
  *
  * Podstawowym celem implementacji tej funkcji jest dokonanie
  * połączenia z bazą danych, a także zapewnienie, że po poprawnym
  * wykonaniu funkcji program będzie zawierał w bazie tabele
  * do dalszej pracy.
  *
  * W przypadku, gdy baza nie jest pusta, procedura ochrony spójności
  * danych opiera się tylko i wyłącznie na badaniu oczekiwanej liczby
  * utworzonych tabel.
  *
  * Zabezpieczenie jest dość proste do złamania przez osoby trzecie.
  * Celem jego istnienia jest wykrywcie możliwych błędów jakie baza
  * mogłaby sama z siebie spowodować.
  *
  * // TODO w kolejnej wersji aplikacji, sprawdź czy program uruchamia
  * sie szybko, jeśli tak zbuduj kod odpowiedzialny za badanie spójności
  * danych.
  *
  * @return true jeśli funkcja zakończy działanie bez zgłaszania błędu,
  *            w przeciwnym wypadku false.
  */
bool LaunchManager::prepare() {
    if ( !connectDatabase() )
        return false;

    if ( isEmptyDatabase() )
        if ( !createAllTables() )
            return false;

    const int32_t currentCountCreatedTables = countCreatedTables();

    if ( currentCountCreatedTables != expectedTablesCount ) {
        EMIT_ERROR( ERROR_DATABASE_LOSE_DATA_CONSISTENCY );
        return false;
    }

    return true;
}

/**
  * Przywrócenie bazy danych do poprawnego stanu.
  *
  * Celem implementacji jest usunięcie zawartości całej bazy danych,
  * aby wykluczyć utratę spójności danych. Następnie przeprowadzana
  * jest operacja mająca na celu utworzenie wszystkich niezbędnych tabel.
  *
  * Uwaga!
  * Funkcja powinna być wywoływana w sytuacji krytycznej, za potwierdzeniem
  * użytkownika, godzącego się na utratę wszystkich wprowadzonych danych.
  *
  * Uwaga!
  * Operacje można realizować tylko po wcześniejszym
  * wywołaniu metody LaunchManager::prepare.
  * @return true jeśli funkcja zakończy działanie bez zgłaszania błędu,
  *            w przeciwnym wypadku false.
  */
bool LaunchManager::restoreState() {
    if ( !resetDatabase() )
        return false;

    if ( !createAllTables() )
        return false;

    return true;
}

/**
  * Uruchamia program.
  *
  * Zachodzi do uruchomienia program na podstawie nazwy launchera,
  * któremu został dopasowany program.
  *
  * Uwaga!
  * Funkcja została utworzona z myślą współpracy z modułem weryfikującym
  * wprowadzone dane przez użytkownika. Co zarazem znaczy, że funkcja
  * nie ocenia poprawność danych. Przy niewłaściwym użytkowaniu funkcji
  * może dość do utracenia spójności danych.
  *
  * Uwaga!
  * Operacje można realizować tylko po wcześniejszym
  * wywołaniu metody LaunchManager::prepare.
  *
  * @param _launcher - nazwa launchera
  *
  * @return true jeśli funkcja zakończy działanie bez zgłaszania błędu,
  *            w przeciwnym wypadku false.
  */
bool LaunchManager::launch( const QString & _launcher ) {
    QSqlQuery query( database );

    query.prepare( QString( sqlCommandSelectFileName ).arg( _launcher ) );

    if ( !query.exec() ) {
        EMIT_ERROR( ERROR_DATABASE_WRONG_QUERY( query ) );
        return false;
    }

    query.last();

    const int32_t countRecords = query.at() + 1;;

    if ( countRecords != 1 ) {
        EMIT_ERROR( ERROR_DATABASE_LOSE_DATA_CONSISTENCY );
        return false;
    }

    const QString fileName =  query.value( 0 ).toString();

    if ( !QFile::exists( fileName ) ) {
        EMIT_ERROR( ERROR_DATABASE_LOSE_DATA_CONSISTENCY );
        return false;
    }

    if ( !QProcess::startDetached( fileName ) ) {
        EMIT_ERROR( ERROR_PROGRAM_WRONG_EXECUTE( fileName ) );
        return false;
    }

    return true;
}

/**
  * Wprowadzenie launcherów do bazy danych.
  *
  * Implementacja funkcji przetwarza wprowadzone dane i rozmieszcza
  * je w odpowiednich tabelach bazy danych.
  *
  * Uwaga!
  * Funkcja została utworzona z myślą współpracy z modułem weryfikującym
  * wprowadzone dane przez użytkownika. Co zarazem znaczy, że funkcja
  * nie ocenia poprawność danych. Przy niewłaściwym użytkowaniu funkcji
  * może dość do utracenia spójności danych.
  *
  * Uwaga!
  * Operacje można realizować tylko po wcześniejszym
  * wywołaniu metody LaunchManager::prepare.
  *
  * @param _launchers - lista launcherów do wprowadzenia
  * @param _fileName - ścieżka dostępu do programu do którego odwołują się launchery
  * @param _icon - ścieżka dostępu do ikony, która reprezentuje program
  *
  * @return true jeśli funkcja zakończy działanie bez zgłaszania błędu,
  *            w przeciwnym wypadku false.
  */
bool LaunchManager::addLaunchers( const QStringList & _launchers, const QString & _fileName,
                                  const QString & _icon ) {
    if ( !addProgram( _fileName, _icon ) )
        return false;

    const int32_t idProgram = getIdProgram( _fileName );

    if ( idProgram == -1 )
        return false;

    foreach( const QString & launcher, _launchers )
    if ( !addLauncher( launcher, idProgram ) )
        return false;

    return true;
}

/**
  * Wprowadzenie launchera do bazy danych.
  *
  * Odwołanie się do bazy danych i przekazanie danych,
  * na rzecz nowego rekordu w tabeli launcherów.
  *
  * Uwaga!
  * Operacje można realizować tylko po wcześniejszym
  * wywołaniu metody LaunchManager::prepare.
  *
  * @param _launcher - launcher do wprowadzenia
  * @param _idProgram - id programu do którego odwolują się launchery
  *
  * @return true jeśli funkcja zakończy działanie bez zgłaszania błędu,
  *            w przeciwnym wypadku false.
  */
bool LaunchManager::addLauncher( const QString & _launcher, const int32_t & _idProgram ) {
    QSqlQuery query( database );

    query.prepare( sqlCommandInsertLauncher.arg( _launcher, QString::number( _idProgram ) ) );

    if ( !query.exec() ) {
        EMIT_ERROR( ERROR_DATABASE_WRONG_QUERY( query ) );
        return false;
    }

    return true;
}

/**
  * Pobranie Id programu z bazy danych.
  *
  * Metoda wykonuje zapytanie do bazy danych i zwraca Id programu,
  * uwzględniając przypadki wadliwego działania, między innymi
  * utraty spójności danych.
  *
  * Uwaga!
  * Operacje można realizować tylko po wcześniejszym
  * wywołaniu metody LaunchManager::prepare.
  *
  * @param _fileName - ścieżka dostępu do programu
  *
  * @return numer id, w przeciwnym razie -1 oznaczający błąd
  */
int32_t LaunchManager::getIdProgram( const QString & _fileName ) {
    QSqlQuery query( database );

    query.prepare( QString( sqlCommandSelectIdProgram ).arg( _fileName ) );

    if ( !query.exec() ) {
        EMIT_ERROR( ERROR_DATABASE_WRONG_QUERY( query ) );
        return -1;
    }

    query.last();

    const int32_t countRecords = query.at() + 1;

    if ( countRecords != 1 ) {
        EMIT_ERROR( ERROR_DATABASE_LOSE_DATA_CONSISTENCY );
        return -1;
    }

    const int32_t idProgram =  query.value( 0 ).toInt();

    return idProgram;
}

/**
  * Edytuje ścieżke dostępu do aplikacji.
  *
  * Uwaga!
  * Funkcja została utworzona z myślą współpracy z modułem weryfikującym
  * wprowadzone dane przez użytkownika. Co zarazem znaczy, że funkcja
  * nie ocenia poprawność danych. Przy niewłaściwym użytkowaniu funkcji
  * może dość do utracenia spójności danych.
  *
  * Uwaga!
  * Operacje można realizować tylko po wcześniejszym
  * wywołaniu metody LaunchManager::prepare.
  *
  * @param _fileName - bieżąca nazwa ścieżki dostępu do aplikacji
  * @param _newFileName - nowa nazwa ścieżki dostępu do aplikacji
  *
  * @return true jeśli funkcja zakończy działanie bez zgłaszania błędu,
  *    w przeciwnym wypadku false.
  */
bool LaunchManager::editFileName( const QString & _fileName, const QString & _newFileName ) {
    if ( getIdProgram( _fileName ) == -1 )
        return false;

    QSqlQuery query( database );

    query.prepare( QString( sqlCommandUpdateFileName ).arg( _fileName, _newFileName ) );

    if ( !query.exec() ) {
        EMIT_ERROR( ERROR_DATABASE_WRONG_QUERY( query ) );
        return false;
    }

    return true;
}

/**
  * Edytuje ścieżke dostępu do ikony.
  *
  * Uwaga!
  * Funkcja została utworzona z myślą współpracy z modułem weryfikującym
  * wprowadzone dane przez użytkownika. Co zarazem znaczy, że funkcja
  * nie ocenia poprawność danych. Przy niewłaściwym użytkowaniu funkcji
  * może dość do utracenia spójności danych.
  *
  * Uwaga!
  * Operacje można realizować tylko po wcześniejszym
  * wywołaniu metody LaunchManager::prepare.
  *
  * @param _fileName - nazwa ścieżki dostępu do aplikacji
  * @param _newIcon - nowa ścieżka dostępu do ikony
  *
  * @return true jeśli funkcja zakończy działanie bez zgłaszania błędu,
  *            w przeciwnym wypadku false.
  */
bool LaunchManager::editIcon( const QString & _fileName, const QString & _newIcon ) {
    if ( getIdProgram( _fileName ) == -1 )
        return false;

    QSqlQuery query( database );

    query.prepare( QString( sqlCommandUpdateIcon ).arg( _fileName, _newIcon ) );

    if ( !query.exec() ) {
        EMIT_ERROR( ERROR_DATABASE_WRONG_QUERY( query ) );
        return false;
    }

    return true;
}

/**
  * Edytuje nazwę launchera.
  *
  * Uwaga!
  * Funkcja została utworzona z myślą współpracy z modułem weryfikującym
  * wprowadzone dane przez użytkownika. Co zarazem znaczy, że funkcja
  * nie ocenia poprawność danych. Przy niewłaściwym użytkowaniu funkcji
  * może dość do utracenia spójności danych.
  *
  * Uwaga!
  * Operacje można realizować tylko po wcześniejszym
  * wywołaniu metody LaunchManager::prepare.
  *
  * @param _launcher - bieżąca nazwa launchera
  * @param _newLauncher - nowa nazwa launchera
  *
  * @return true jeśli funkcja zakończy działanie bez zgłaszania błędu,
  *            w przeciwnym wypadku false.
  */
bool LaunchManager::editLauncher( const QString & _launcher, const QString & _newLauncher ) {
    bool isError = false;
    bool isExists = isExistsLauncher( _launcher, isError );

    if (( !isError ) && ( !isExists ) ) {
        EMIT_ERROR( ERROR_DATABASE_LOSE_DATA_CONSISTENCY );
        return false;
    }

    QSqlQuery query( database );

    query.prepare( QString( sqlCommandUpdateLauncher ).arg( _launcher, _newLauncher ) );

    if ( !query.exec() ) {
        EMIT_ERROR( ERROR_DATABASE_WRONG_QUERY( query ) );
        return false;
    }

    return true;
}

/**
  * Usuwa program z bazy danych.
  *
  * W przed usunięciem programu usuwana są wszystkie
  * launchery pozwiązane z danym programem.
  *
  * Uwaga!
  * Funkcja została utworzona z myślą współpracy z modułem weryfikującym
  * wprowadzone dane przez użytkownika. Co zarazem znaczy, że funkcja
  * nie ocenia poprawność danych. Przy niewłaściwym użytkowaniu funkcji
  * może dość do utracenia spójności danych.
  *
  * Uwaga!
  * Operacje można realizować tylko po wcześniejszym
  * wywołaniu metody LaunchManager::prepare.
  *
  * @param _fileName - ścieżka dostępu do usuwanego programu z bazy danych
  *
  * @return true jeśli funkcja zakończy działanie bez zgłaszania błędu,
  *            w przeciwnym wypadku false.
  */
bool LaunchManager::removeProgram( const QString & _fileName ) {
    const int32_t idProgram = getIdProgram( _fileName );

    if ( idProgram == -1 )
        return false;

    removeAllProgramLauncher( idProgram );

    QSqlQuery query( database );

    query.prepare( QString( sqlCommandRemoveProgram ).arg( idProgram ) );

    if ( ! query.exec() ) {
        EMIT_ERROR( ERROR_DATABASE_WRONG_QUERY( query ) );
        return false;
    }

    return true;
}

/**
  * Usuwa wszystkie launchery programu o wskazanym Id.
  *
  * Uwaga!
  * Funkcja została utworzona z myślą współpracy z modułem weryfikującym
  * wprowadzone dane przez użytkownika. Co zarazem znaczy, że funkcja
  * nie ocenia poprawność danych. Przy niewłaściwym użytkowaniu funkcji
  * może dość do utracenia spójności danych.
  *
  * Uwaga!
  * Operacje można realizować tylko po wcześniejszym
  * wywołaniu metody LaunchManager::prepare.
  *
  * @param _idProgram - id programu
  *
  * @return true jeśli funkcja zakończy działanie bez zgłaszania błędu,
  *            w przeciwnym wypadku false.
  */
bool LaunchManager::removeAllProgramLauncher( const int32_t _idProgram ) {
    QSqlQuery query( database );

    query.prepare( QString( sqlCommandRemoveAllProgramLaunchers ).arg( _idProgram ) );

    if ( ! query.exec() ) {
        EMIT_ERROR( ERROR_DATABASE_WRONG_QUERY( query ) );
        return false;
    }

    return true;
}

/**
  * Usuwa wybrany launcher z bazy danych.
  *
  * Uwaga!
  * Funkcja została utworzona z myślą współpracy z modułem weryfikującym
  * wprowadzone dane przez użytkownika. Co zarazem znaczy, że funkcja
  * nie ocenia poprawność danych. Przy niewłaściwym użytkowaniu funkcji
  * może dość do utracenia spójności danych.
  *
  * Uwaga!
  * Operacje można realizować tylko po wcześniejszym
  * wywołaniu metody LaunchManager::prepare.
  *
  * @param _launcher - nazwa launchera
  *
  * @return true jeśli funkcja zakończy działanie bez zgłaszania błędu,
  *            w przeciwnym wypadku false.
  */
bool LaunchManager::removeLauncher( const QString & _launcher ) {
    QSqlQuery query( database );

    query.prepare( QString( sqlCommandRemoveLauncher ).arg( _launcher ) );

    if ( ! query.exec() ) {
        EMIT_ERROR( ERROR_DATABASE_WRONG_QUERY( query ) );
        return false;
    }

    return true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// Private Method
///////////////////////////////////////////////////////////////////////////////////////////////////

/**
  * Dokonanie połączenia z bazą danych.
  *
  * Podstawą poprawnej realizacji danej funkcji jest
  * wykrycie, czy zachodzi próba dokonania reconnectu
  * uznawanego jako błąd oraz czy program wykrywa
  * sterownik pozwalający aplikacji korzystać z bazy danych.
  *
  * @return true jeśli funkcja zakończy działanie bez zgłaszania błędu,
  *            w przeciwnym wypadku false.
  */
bool LaunchManager::connectDatabase() {
    if ( database.isOpen() ) {
        EMIT_ERROR( ERROR_DATABASE_BLOCKED_RECONNECT );
        return false;
    }

    if ( !isExistsDriver() ) {
        EMIT_ERROR( ERROR_DATABASE_DRIVER_NOT_EXISTS );
        return false;
    }

    database = QSqlDatabase::addDatabase( driver );

    database.setDatabaseName( databaseFileName );

    if ( !database.open() ) {
        EMIT_ERROR( ERROR_DATABASE_WRONG_OPEN( database ) );
        return false;
    }

    return true;
}

/**
  * Utworzenie wszystkich tabel w bazie danych.
  *
  * Uwaga:
  * Jeżeli w trakcie działania metody będzie istnieć choć tylko jedna
  * tabela to sytuacja oznacza błąd w działaniu aplikacji.
  *
  * Uwaga!
  * Operacje można realizować tylko po wcześniejszym
  * wywołaniu metody LaunchManager::prepare.
  *
  * @return true jeśli funkcja zakończy działanie bez zgłaszania błędu,
  *            w przeciwnym wypadku false.
  */
bool LaunchManager::createAllTables() {
    Q_ASSERT( countCreatedTables() != 0 );

    QSqlQuery query( database );

    if ( !query.exec( sqlCommandCreateTablePrograms ) ) {
        EMIT_ERROR( ERROR_DATABASE_WRONG_QUERY( query ) );
        return false;
    }

    if ( !query.exec( sqlCommandCreateTableLaunchers ) ) {
        EMIT_ERROR( ERROR_DATABASE_WRONG_QUERY( query ) );
        return false;
    }

    Q_ASSERT( countCreatedTables() == expectedTablesCount );

    return true;
}

/**
  * Usunięcie obu tabel.
  *
  * Zarówno tabela Launchers jak i Programs może nie istnieć.
  * Ta sytuacja nie wyraża błędu aplikacji.
  *
  * Uwaga!
  * Operacje można realizować tylko po wcześniejszym
  * wywołaniu metody LaunchManager::prepare.
  *
  * @return true jeśli funkcja zakończy działanie bez zgłaszania błędu,
  *            w przeciwnym wypadku false.
  */
bool LaunchManager::resetDatabase() {
    Q_ASSERT( countCreatedTables() != 0 );

    QSqlQuery query( database );

    if ( !query.exec( sqlCommandDropTablePrograms ) ) {
        EMIT_ERROR( ERROR_DATABASE_WRONG_QUERY( query ) );
        return false;
    }

    if ( !query.exec( sqlCommandDropTableLaunchers ) ) {
        EMIT_ERROR( ERROR_DATABASE_WRONG_QUERY( query ) );
        return false;
    }

    if ( countCreatedTables() != 0 ) {
        EMIT_ERROR( ERROR_DATABASE_LOSE_DATA_CONSISTENCY );
        return false;
    }

    return true;
}

/**
  * Dodanie programu do bazy danych.
  *
  * Uwaga!
  * Operacje można realizować tylko po wcześniejszym
  * wywołaniu metody LaunchManager::prepare.
  *
  * @return true jeśli funkcja zakończy działanie bez zgłaszania błędu,
  *            w przeciwnym wypadku false.
  */
bool LaunchManager::addProgram( const QString & _fileName, const QString & _icon ) {
    QSqlQuery query( database );

    if ( _icon.isEmpty() )
        query.prepare( sqlCommandInsertProgramWithDefaultIcon.arg( _fileName ) );
    else
        query.prepare( sqlCommandInsertProgram.arg( _fileName, _icon ) );

    if ( !query.exec() ) {
        EMIT_ERROR( ERROR_DATABASE_WRONG_QUERY( query ) );
        return false;
    }

    return true;
}

/**
  * Sprawdzenie, czy sterownik bazy danych jest dostępny.
  *
  * @return true jeśli jest dostępny, w przeciwnym wypadku false.
  */
bool LaunchManager::isExistsDriver() {
    return QSqlDatabase::drivers().contains( driver );
}

/**
  * Sprawdzenie, czy baza danych jest pusta.
  *
  * Uwaga!
  * Operacje można realizować tylko po wcześniejszym
  * wywołaniu metody LaunchManager::prepare.
  *
  * @return true, jeśli jest pusta, w przeciwnym wypadku false.
  */
bool LaunchManager::isEmptyDatabase() {
    return !countCreatedTables();
}

/**
  * Sprawdzenie, czy ścieżka dostępu do programu istnieje.
  *
  * Wykrycie nadmiaru ścieżek dostępu o tej samej nazwie traktowane
  * jest jako wykrycie braku spójności w bazie danych. Sytuacja
  * uważana jest za poważny błąd aplikacji.
  *
  * Uwaga!
  * Operacje można realizować tylko po wcześniejszym
  * wywołaniu metody LaunchManager::prepare.
  *
  * Po wywołaniu funkcji należy sprawdzić, czy zaszedł błąd w funkcji.
  * W tym celu należy użyć parametru _isError, którzy przechowuje informacje
  * o tym zdarzeniu.
  *
  * @param _fileName - ścieżka dostępu do programu
  * @param _isError - zmienna dowiadujaca się, czy zaszedł błąd
  *
  * @return true, jeśli istnieje, w przeciwnym wypadku false.
  */
bool LaunchManager::isExistsFileName( const QString & _fileName, bool & _isError ) {
    _isError = false;
    QSqlQuery query( database );

    query.prepare( QString( sqlCommandSelectIdProgram ).arg( _fileName ) );

    if ( !query.exec() ) {
        EMIT_ERROR( ERROR_DATABASE_WRONG_QUERY( query ) );
        _isError = true;
        return false;
    }

    query.last();

    const int32_t countRecords = query.at() + 1;

    if ( countRecords > 1 ) {
        EMIT_ERROR( ERROR_DATABASE_LOSE_DATA_CONSISTENCY );
        _isError = true;
        return true;
    }

    return countRecords == 1;
}

/**
  * Sprawdzenie, czy launcher istnieje.
  *
  * Wykrycie nadmiaru launcherów o tej samej nazwie traktowane
  * jest jako wykrycie braku spójności w bazie danych. Sytuacja
  * uważana jest za poważny błąd aplikacji.
  *
  * Uwaga!
  * Operacje można realizować tylko po wcześniejszym
  * wywołaniu metody LaunchManager::prepare.
  *
  * Po wywołaniu funkcji należy sprawdzić, czy zaszedł błąd w funkcji.
  * W tym celu należy użyć parametru _isError, którzy przechowuje informacje
  * o tym zdarzeniu.
  *
  * @param _launcher - nazwa launchera
  * @param _isError - zmienna dowiadujaca się, czy zaszedł błąd
  *
  * @return true, jeśli istnieje, w przeciwnym wypadku false.
  */
bool LaunchManager::isExistsLauncher( const QString & _launcher, bool & _isError ) {
    _isError = false;
    QSqlQuery query( database );

    query.prepare( QString( sqlCommandSelectFileName ).arg( _launcher ) );

    if ( !query.exec() ) {
        EMIT_ERROR( ERROR_DATABASE_WRONG_QUERY( query ) );
        _isError = true;
        return false;
    }

    query.last();

    const int32_t countRecords = query.at() + 1;

    if ( countRecords > 1 ) {
        EMIT_ERROR( ERROR_DATABASE_LOSE_DATA_CONSISTENCY );
        _isError = true;
        return true;
    }

    return countRecords == 1;
}

/**
  * Zliczanie ilości utworzonych tabel.
  *
  * Uwaga!
  * Operacje można realizować tylko po wcześniejszym
  * wywołaniu metody LaunchManager::prepare.
  *
  * @return ilosc utworzonych tabel.
  */
int32_t LaunchManager::countCreatedTables() {
    const QStringList tables = database.tables();
    int32_t count = tables.count();

    // tabela 'sqlite_sequence' jest na stałe zatwierdzona
    // w bazie, dlatego też, muszę ją za każdym razem wykluczać

    if ( tables.contains( "sqlite_sequence" ) )
        --count;

    return count;
}
